﻿//======================================================================
//
//	Cross-Platform XNA Menu System
//  http://cpxms.codeplex.com/
//
//  All code and content licensed under Microsoft Public License (Ms-PL)	
//
//======================================================================
//
//	File:		PageFlipPostProcessor.cs
//
//	Author:		MJP
//
//	Date:		05/12/09
//
//======================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.Drawing.Design;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace CPXMenu
{
	/// <summary>
	/// A class used to perform post-processing for PageFlipEffects.
	/// </summary>
    internal class PageFlipPostProcessor
    {
        private Dictionary<string, List<RenderTarget2D>> renderTargets = new Dictionary<string, List<RenderTarget2D>>();
        private List<RenderTarget2D> usedTargets = new List<RenderTarget2D>();
        private GraphicsDevice graphicsDevice;
        private ContentManager contentManager;
        private FullScreenQuad quad;
        private Effect flipEffects;

		/// <summary>
		/// Creates a new instance of PageFlipPostProcessor
		/// </summary>
		/// <param name="graphicsDevice">The GraphicsDevice to user for rendering</param>
		/// <param name="contentManager">The ContentManager from which content can be loaded</param>
        public PageFlipPostProcessor(GraphicsDevice graphicsDevice, ContentManager contentManager)
        {
            this.graphicsDevice = graphicsDevice;
            this.contentManager = contentManager;
            quad = new FullScreenQuad(graphicsDevice);
            this.flipEffects = contentManager.Load<Effect>("CPXMenu Content\\Effects\\PageFlipEffects");
        }

		/// <summary>
		/// Blends two images together
		/// </summary>
		/// <param name="source0">The first source image</param>
		/// <param name="source1">The second source image</param>
		/// <param name="result">The RenderTarget where the result should be written to0</param>
		/// <param name="blendFactor">The amount to lerp between the two images</param>
        public void Blend(RenderTarget2D source0, RenderTarget2D source1, RenderTarget2D result, float blendFactor)
        {
            blendFactor = MathHelper.Clamp(blendFactor, 0, 1);

            flipEffects.Parameters["g_fBlendFactor"].SetValue(blendFactor);

            RenderTarget2D[] sources = new RenderTarget2D[2];
            sources[0] = source0;
            sources[1] = source1;
            DoPostProcess(sources, result, flipEffects.Techniques["Blend"]);
        }

		/// <summary>
		/// Blurs the source image
		/// </summary>
		/// <param name="source">The source image</param>
		/// <param name="result">The result image</param>
		/// <param name="blurAmount">The amount by which the image should be blurred</param>
        public void Blur(RenderTarget2D source, RenderTarget2D result, float blurAmount)
        {
            RenderTarget2D downscale = GetRenderTarget(source.Width / 4, source.Height / 4);
            RenderTarget2D blurH = GetRenderTarget(downscale.Width, downscale.Height);
            RenderTarget2D blurV = downscale;
            RenderTarget2D upscale = GetRenderTarget(source.Width, source.Height);

            DoPostProcess(source, downscale, flipEffects.Techniques["Simple"]);

            DoPostProcess(downscale, blurH, flipEffects.Techniques["BlurH"]);

            DoPostProcess(blurH, blurV, flipEffects.Techniques["BlurV"]);

            DoPostProcess(blurV, upscale, flipEffects.Techniques["Simple"]);

            Blend(source, upscale, result, blurAmount);
        }

		/// <summary>
		/// Clears all created render targets
		/// </summary>
        public void ClearRenderTargets()
        {
            ReleaseAllRenderTargets();

            foreach (List<RenderTarget2D> targetList in renderTargets.Values)
                foreach (RenderTarget2D target in targetList)
                    target.Dispose();
            renderTargets.Clear();
        }

		/// <summary>
		/// Gets a render target from the cache if one is available, otherwise one is created
		/// </summary>
		/// <param name="width">The width of the render target</param>
		/// <param name="height">The height of the render target</param>
		/// <returns>A render target from the cache if one is available, otherwise one is created</returns>
        public RenderTarget2D GetRenderTarget(int width, int height)
        {
            string sizeString = width.ToString() + "x" + height.ToString();
            List<RenderTarget2D> targetList;
            if (renderTargets.ContainsKey(sizeString))
                targetList = renderTargets[sizeString];
            else
            {
                targetList = new List<RenderTarget2D>();
                renderTargets.Add(sizeString, targetList);
            }

            foreach (RenderTarget2D target in targetList)
            {
                if (!usedTargets.Contains(target))
                {
                    usedTargets.Add(target);
                    return target;
                }
            }

            RenderTarget2D newTarget = new RenderTarget2D(graphicsDevice, width, height, 1, SurfaceFormat.Color);
            targetList.Add(newTarget);
            usedTargets.Add(newTarget);
            return newTarget;
        }

		/// <summary>
		/// Gets a render target from the cache if one is available, otherwise one is created
		/// </summary>
		/// <param name="size">The dimensions of the render target</param>
		/// <returns>A render target from the cache if one is available, otherwise one is created</returns>
        public RenderTarget2D GetRenderTarget(Point size)
        {
            return GetRenderTarget(size.X, size.Y);
        }

		/// <summary>
		/// Releases a render target, indicating it can be used again
		/// </summary>
		/// <param name="renderTarget">The render target to release</param>
        public void ReleaseRenderTarget(RenderTarget2D renderTarget)
        {
            if (usedTargets.Contains(renderTarget))
                usedTargets.Remove(renderTarget);
        }

		/// <summary>
		/// Releases all render targets so that they can be used again
		/// </summary>
        public void ReleaseAllRenderTargets()
        {
            usedTargets.Clear();
        }

		/// <summary>
		/// Performs a post process on the specified source texture
		/// </summary>
		/// <param name="source">The texture to use as input</param>
		/// <param name="result">The texture to render the result to</param>
		/// <param name="technique">The technique to use</param>
        private void DoPostProcess(RenderTarget2D source, RenderTarget2D result, EffectTechnique technique)
        {
            RenderTarget2D[] sources = new RenderTarget2D[1];
            sources[0] = source;
            DoPostProcess(sources, result, technique);
        }

		/// <summary>
		/// Performs a post process on the specified source textures
		/// </summary>
		/// <param name="sources">The textures to use as input</param>
		/// <param name="result">The texture to render the result to</param>
		/// <param name="technique">The technique to use</param>
        private void DoPostProcess(RenderTarget2D[] sources, RenderTarget2D result, EffectTechnique technique)
        {
            graphicsDevice.SetRenderTarget(0, result);
            graphicsDevice.Clear(ClearOptions.Target, Color.Black, 1, 0);

            for (int i = 0; i < sources.Length; i++)
            {
                flipEffects.Parameters["Texture" + i].SetValue(sources[i].GetTexture());
                flipEffects.Parameters["g_vTextureSize" + i].SetValue(new Vector2(sources[i].Width, sources[i].Height));
            }

            if (result != null)
                flipEffects.Parameters["g_vDestinationSize"].SetValue(new Vector2(result.Width, result.Height));
            else
                flipEffects.Parameters["g_vDestinationSize"].SetValue(new Vector2(graphicsDevice.PresentationParameters.BackBufferWidth,
                                                                                    graphicsDevice.PresentationParameters.BackBufferHeight));

            flipEffects.CurrentTechnique = technique;
            flipEffects.Begin();
            flipEffects.CurrentTechnique.Passes[0].Begin();

            quad.Draw(graphicsDevice);

            flipEffects.CurrentTechnique.Passes[0].End();
            flipEffects.End();
        }
    }
}